package org.jgrasstools.gears.io.disktree.jtstmp;

import java.io.Serializable;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.util.Assert;

/**
 * Represents a node of a {@link Quadtree}.  Nodes contain
 * items which have a spatial extent corresponding to the node's position
 * in the quadtree.
 *
 * @version 1.7
 */
public class Node extends NodeBase implements Serializable {
    
    private static final long serialVersionUID = 6549373230015282305L;

    public static Node createNode( Envelope env ) {
        Key key = new Key(env);
        Node node = new Node(key.getEnvelope(), key.getLevel());
        return node;
    }

    public static Node createExpanded( Node node, Envelope addEnv ) {
        Envelope expandEnv = new Envelope(addEnv);
        if (node != null)
            expandEnv.expandToInclude(node.env);

        Node largerNode = createNode(expandEnv);
        if (node != null)
            largerNode.insertNode(node);
        return largerNode;
    }

    private Envelope env;
    private Coordinate centre;
    private int level;

    public Node( Envelope env, int level ) {
        // this.parent = parent;
        this.env = env;
        this.level = level;
        centre = new Coordinate();
        centre.x = (env.getMinX() + env.getMaxX()) / 2;
        centre.y = (env.getMinY() + env.getMaxY()) / 2;
    }

    public Envelope getEnvelope() {
        return env;
    }

    protected boolean isSearchMatch( Envelope searchEnv ) {
        return env.intersects(searchEnv);
    }

    /**
     * Returns the subquad containing the envelope.
     * Creates the subquad if
     * it does not already exist.
     */
    public Node getNode( Envelope searchEnv ) {
        int subnodeIndex = getSubnodeIndex(searchEnv, centre);
        // if subquadIndex is -1 searchEnv is not contained in a subquad
        if (subnodeIndex != -1) {
            // create the quad if it does not exist
            Node node = getSubnode(subnodeIndex);
            // recursively search the found/created quad
            return node.getNode(searchEnv);
        } else {
            return this;
        }
    }

    /**
     * Returns the smallest <i>existing</i>
     * node containing the envelope.
     */
    public NodeBase find( Envelope searchEnv ) {
        int subnodeIndex = getSubnodeIndex(searchEnv, centre);
        if (subnodeIndex == -1)
            return this;
        if (subnode[subnodeIndex] != null) {
            // query lies in subquad, so search it
            Node node = subnode[subnodeIndex];
            return node.find(searchEnv);
        }
        // no existing subquad, so return this one anyway
        return this;
    }

    void insertNode( Node node ) {
        Assert.isTrue(env == null || env.contains(node.env));
        // System.out.println(env);
        // System.out.println(quad.env);
        int index = getSubnodeIndex(node.env, centre);
        // System.out.println(index);
        if (node.level == level - 1) {
            subnode[index] = node;
            // System.out.println("inserted");
        } else {
            // the quad is not a direct child, so make a new child quad to contain it
            // and recursively insert the quad
            Node childNode = createSubnode(index);
            childNode.insertNode(node);
            subnode[index] = childNode;
        }
    }

    /**
     * get the subquad for the index.
     * If it doesn't exist, create it
     */
    private Node getSubnode( int index ) {
        if (subnode[index] == null) {
            subnode[index] = createSubnode(index);
        }
        return subnode[index];
    }

    private Node createSubnode( int index ) {
        // create a new subquad in the appropriate quadrant

        double minx = 0.0;
        double maxx = 0.0;
        double miny = 0.0;
        double maxy = 0.0;

        switch( index ) {
        case 0:
            minx = env.getMinX();
            maxx = centre.x;
            miny = env.getMinY();
            maxy = centre.y;
            break;
        case 1:
            minx = centre.x;
            maxx = env.getMaxX();
            miny = env.getMinY();
            maxy = centre.y;
            break;
        case 2:
            minx = env.getMinX();
            maxx = centre.x;
            miny = centre.y;
            maxy = env.getMaxY();
            break;
        case 3:
            minx = centre.x;
            maxx = env.getMaxX();
            miny = centre.y;
            maxy = env.getMaxY();
            break;
        }
        Envelope sqEnv = new Envelope(minx, maxx, miny, maxy);
        Node node = new Node(sqEnv, level - 1);
        return node;
    }

}
